プレゼンテーションカラオケサービスをlit-htmlで作ってみた
大阪オフィスの岡田です。 2020年も1ヶ月半が過ぎましたが、2019年の弊社大阪オフィスの忘年会の出し物として プレゼンテーションカラオケ をやるのにあたり、サービスをサクッと作りました。
技術を試してみるという意味もあったので、サクッとブログに残しておきます。
経緯
私の所属している大阪オフィスでは忘年会で プレゼンテーションカラオケ(パワ-ポイントカラオケ) をやることになったのですが、その決定現場にたまたま居合わせたため、サービスを作ってみようと手を上げました。
最近はAPIサーバばかり作っていたので、久々にフロントエンドも触っておこうという下心もあり、一石二鳥です。
プレゼンテーションカラオケとは?
制限時間5分でランダムに表示される画像に対して即興でプレゼンテーションします。 詳細はこちらを御覧ください。 (参照:日本プレゼンカラオケ協会)
作ったもの(2020/02/14追記)
デモです。
技術と選定理由
要件は 画像をランダムに表示させる のみなので、フロントエンドだけで完結出来ると考えました。
後になって気づいたことですが、この時点では忘年会会場の電波状態がわからなかったので、この判断は正しかったですね。
個人的にもPolymerを触ったことがあり、今回は lit-html
に挑戦することにしました。
- node
- yarn
- lit-html
lit-htmlとは?
lit-htmlは、JavaScriptでHTMLテンプレートを書くことができ(HTML in JS)、DOMを作成・更新するのに必要なデータとテンプレートを効率的に描画・再描画します (参照:https://lit-html.polymer-jp.org/)
WEBComponent を利用したHTMLのレンダリングを行うライブラリです。
JSXのような感じですが、VDOMを利用しておらず、ネイティブクローニングをによるDOMの更新を行うのが特徴です。
提供される関数は html
render
の2つで、html で要素を作り、renderで描画します。
ソースコード
全体は github に公開しています。 メインのアプリケーション部分はこんな感じ。
import { html, render } from 'lit-html' import { images } from './images.js' const MAX_SLIDES = 4 // 配列をシャッフルしてMAX_SLIDES枚返す const shuffleImages = imgs => { const shuffledImgs = imgs .map(a => [Math.random(), a]) .sort((a, b) => a[0] - b[0]) .map((a, i) => { return { path: `./images/${a[1]}`, pageNo: i + 1 } }) return shuffledImgs.slice(0, MAX_SLIDES) } // 状態を更新する const createStore = () => { let state = {} return newState => { if (!state.slides || newState) { if (!state.slides || newState.slideIndex === undefined) { const slides = [ { path: './resources/PPK_first.png', first: true }, ...shuffleImages(images), { path: './resources/PPK_end.png', last: true }, ] state = { ...state, ...newState, slides } } else { state = { ...state, ...newState } } renderApp() } return state } } const store = createStore() // スライドを開始 const startSlide = () => { store({ slideIndex: 0 }) } // 次のスライド const nextSlide = () => { const { slideIndex, slides } = store() const slide = slides[slideIndex] if (slide && slide.last === undefined) { store({ slideIndex: slideIndex + 1 }) } else { console.warn(`Not have next slide. index = ${slideIndex}`) } } // 前のスライド const prevSlide = () => { const { slideIndex } = store() if (slideIndex > 0) { store({ slideIndex: slideIndex - 1 }) } else { console.warn(`Not have prev slide. index = ${slideIndex}`) } } // タイトルに戻る const backToTitle = () => { store({ slideIndex: undefined }) } // スライド用のStyle const slideStyle = path => { return ` position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: black; background-image: url("${path}"); background-size: contain; background-position: center; background-repeat: no-repeat; ` } // スライドのページ(html) const slidePage = () => { const { slideIndex, slides } = store() console.debug('image:', slides[slideIndex]) const path = slides[slideIndex].path const pageNo = slides[slideIndex].pageNo const pageInfo = html` <div style="color: white; text-align: right; position: relative; z-index: 100;" > ${pageNo}/${MAX_SLIDES} </div> ` return html` ${pageNo && pageInfo} <div @click=${slides[slideIndex].last ? backToTitle : nextSlide} style=${slideStyle(path)} /> ` } const page = () => { const { slideIndex, slides } = store() if (slideIndex === undefined) { return html` <div @click=${startSlide} style=${slideStyle('./resources/PPK_titleback.png')} /> ` } else { return html` ${slidePage()} ` } } // pageを描画する function renderApp() { return render(page(), document.body) } // キー操作系 右・下・Enterで次のスライド/左・上で前のスライド document.addEventListener('keydown', e => { const key = e.key switch (e.key) { case 'ArrowRight': case 'ArrowDown': case 'Enter': nextSlide() break case 'ArrowLeft': case 'ArrowUp': prevSlide() break } }) renderApp()
コマンド
$ yarn create:images // /resources 内にある画像ファイル名の配列を作る(サーバ起動前) $ polymer serve` //サーバ起動 $ polymer build` //ビルド
ハマりどころ
状態をどこで持つか?
React等では標準搭載されている機能かと思いますが、lit-htmlにはありません。より関数的に作れるのでは個人的には好みなのですが、プレゼンテーションの状態によって表示するページを出し分けたかったので、状態を管理する必要が出てきました。
こちらの記事 を参考にさせていただき、自前で実装しています。
画像の情報をどうやってもたせるか
これは lit-html は関係ない話ですが、フロントエンドのみで画像をランダム表示させるのに、画像情報をどうやってもたせるかを悩んでいました。色々悩んだ末、画像名の配列を出力する npm script を用意することにしました。もっといいやり方があったら教えて下さい。
まとめ
機能がシンプルすぎるため、lit-htmlだけでサービスを作ることは難しい印象でした。
依存ライブラリも少ないため、最終的に作られるものの容量を意識しなければ行けない場面(lambda等) では威力を発揮するかもしれません。
肝心のプレゼンテーションカラオケは大盛況で、忘年会だけで収まらず、納会でも実施されるほどでした。